﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using Properties;

namespace Borg
{
    public abstract class RuntimeBase
    {
        protected internal bool isStartup = true;
        protected VerbosityLevel verbosityLevel = VerbosityLevel.Normal;

        internal bool lazyLoad = false;
        internal bool autoOpen = true;
        internal bool autoRegister = true;
        internal bool autoPlug = true;
        internal bool autoRelease = true;
        internal bool isCompositionAllowed = true;

        private readonly Stack<InvokeMethodInfo> _invokeOnCompositionDoneStack =
            new Stack<InvokeMethodInfo>();

        protected internal event LogEventHandler LogEvent;
        protected internal event PropertyChangedEventHandler PropertyChanged;
        protected internal event EventHandler StartupCompleted;

        protected internal DirectoryInfo rootDirectory;
        internal Dictionary<string, string> arguments;
        internal ReadOnlyCollection<string> baseDirectories;
        protected internal DateTime startTime;
        protected internal bool isFinalizing;

        private readonly InvokeEventDelegate _invokeEventHandler;

        internal TaskQueue taskQueue;
        protected internal Composer composer;
        protected internal Repository repository;
        protected internal ExtensionInfo extension;
        protected internal LogBuffer logBuffer;
        protected readonly RuntimeConfiguration configuration;

        public ReadOnlyCollection<string> BaseDirectories
        {
            get { return baseDirectories; }
        }

        public TaskQueue TaskQueue
        {
            get { return taskQueue; }
        }

        public Composer Composer
        {
            get { return composer; }
        }

        public LogBuffer LogBuffer
        {
            get { return logBuffer; }
        }

        protected internal virtual Repository Repository
        {
            get { return repository; }
        }

        public VerbosityLevel VerbosityLevel
        {
            get { return verbosityLevel; }
            set { verbosityLevel = value; }
        }

        public TimeSpan UpTime
        {
            get { return DateTime.Now.Subtract(startTime); }
        }

        public virtual bool AutoOpen
        {
            get { return autoOpen; }
            set
            {
                if (!Runtime.IsCompositionAllowed)
                    throw new InvalidOperationException("Composition not allowed");

                Runtime.Composer.SetAutoOpen(this, value);
            }
        }

        public virtual bool AutoRegister
        {
            get { return autoRegister; }
            set
            {
                if (!Runtime.IsCompositionAllowed)
                    throw new InvalidOperationException("Composition not allowed");

                Runtime.Composer.SetAutoRegister(this, value);
            }
        }

        public virtual bool AutoPlug
        {
            get { return autoPlug; }
            set
            {
                if (!Runtime.IsCompositionAllowed)
                    throw new InvalidOperationException("Composition not allowed");

                Runtime.Composer.SetAutoPlug(this, value);
            }
        }

        public virtual bool AutoRelease
        {
            get { return autoRelease; }
            set
            {
                if (!Runtime.IsCompositionAllowed)
                    throw new InvalidOperationException("Composition not allowed");

                Runtime.Composer.SetAutoRelease(this, value);
            }
        }

        public virtual bool LazyLoad
        {
            get { return lazyLoad; }
            set
            {
                if (!Runtime.IsCompositionAllowed)
                    throw new InvalidOperationException("Composition not allowed");

                Runtime.Composer.SetLazyLoad(this, value);
            }
        }

        public virtual bool IsCompositionAllowed
        {
            get { return isCompositionAllowed; }
        }

        public virtual ExtensionInfo ExtensionInfo
        {
            get { return extension; }
        }

        protected internal virtual RuntimeBase Instance
        {
            get { return this; }
        }

        protected internal abstract bool ConsoleWindow { get; set; }

        protected RuntimeBase(RuntimeConfiguration configuration)
        {
            this.configuration = configuration;
            startTime = DateTime.Now;
            rootDirectory = configuration.RootDirectory;
            arguments = configuration.Arguments ?? new Dictionary<string, string>();
            baseDirectories = new ReadOnlyCollection<string>(configuration.BaseDirectories ?? new List<string>());

            if (Runtime.runtime == null)
                Runtime.runtime = this;

            Runtime.assembly = GetType().Assembly;
            logBuffer = new LogBuffer(configuration.LogbufferSize);
            repository = configuration.Repository ?? new Repository();
            verbosityLevel = configuration.VerbosityLevel;
            taskQueue = new TaskQueue();
            composer = new Composer();
            _invokeEventHandler = InvokeEventHandler;
        }

        protected static TypeBuilder CreateTypeBuilder()
        {
            return new TypeBuilder();
        }

        internal static void SetProperty(object value, ref object target, string propertyName)
        {
            target = value;
        }

        protected internal abstract bool IsRuntimeThread();

        protected internal abstract object Invoke(Delegate method, params object[] args);

        public void InvokeOnCompositionDone(ObjectParameterHandler handler, object args)
        {
            Runtime.Invoke(
                new InvokeMethodInfoDelegate(
                    InvokeOnCompositionDone),
                new object[]
                    {
                        new InvokeMethodInfo(handler, new[] {args})
                    });
        }

        public Composer DisableComposition()
        {
            if (!isCompositionAllowed)
                throw new InvalidOperationException("Composition is already disabled");

            isCompositionAllowed = false;
            return composer;
        }

        private void InvokeOnCompositionDone(InvokeMethodInfo methodInfo)
        {
            if (taskQueue.IsBusy)
                _invokeOnCompositionDoneStack.Push(methodInfo);
            else
                methodInfo.InvokeMethod();
        }

        protected virtual void AddInitialContract(ContractInfo contractInfo)
        {
            if (repository.contracts.ContainsKey(contractInfo.repositoryId))
                return;

            repository.InternalAddContract(contractInfo);
        }

        protected virtual void AddInitialPlugin(PluginInfo pluginInfo)
        {
            if (repository.plugins.ContainsKey(pluginInfo.repositoryId))
                return;

            repository.InternalAddPlugin(pluginInfo);
        }

        protected void AddContract(ContractInfo contractInfo, uint taskGroupId)
        {
            repository.AddContract(contractInfo, taskGroupId);
        }

        protected void AddPlugin(PluginInfo pluginInfo, uint taskGroupId)
        {
            repository.AddPlugin(pluginInfo, taskGroupId);
        }

        protected void RemoveContract(ContractInfo contractInfo, uint taskGroupId)
        {
            repository.RemoveContract(contractInfo, taskGroupId);
        }

        protected void RemovePlugin(PluginInfo pluginInfo, uint taskGroupId)
        {
            repository.RemovePlugin(pluginInfo, taskGroupId);
        }

        protected void RegisterInitialPlugin(PluginInfo pluginInfo)
        {
            taskQueue.Enqueue(
                TaskFactory.CreateRegister(
                    pluginInfo,
                    false,
                    TaskFactory.CreateTaskGroupId(),
                    Runtime.assembly));
        }

        protected void CreateRuntimeExtension(ExtensionTypeInfo extensionType, object extensionObject)
        {
            extension = new ExtensionInfo(extensionType, extensionObject);
            extensionType.extensionInfos.Add(extension);
            SetPluginAssembly(extensionType.PluginInfo, extensionObject.GetType().Assembly);
            repository.objects.Add(extensionObject, extension);
        }

        protected internal static void SetPluginAssembly(PluginInfo pluginInfo, Assembly assembly)
        {
            pluginInfo.assembly = assembly;

            foreach (var extensionTypeInfo in pluginInfo.extensionTypeInfos)
                extensionTypeInfo.type = assembly.GetType(extensionTypeInfo.typeName);
        }

        protected internal void SetContractAssembly(ContractInfo contractInfo, Assembly assembly)
        {
            contractInfo.assembly = assembly;

            foreach (SlotDefinition slotDefinition in contractInfo.slotDefinitions)
                slotDefinition.type = assembly.GetType(slotDefinition.typeName);
        }

        protected internal virtual void Shutdown(Assembly initiator)
        {
            isFinalizing = true;
            extension.InternalCloseSlots(Runtime.assembly, true);
            ReleaseRest(Runtime.assembly);
        }

        protected internal virtual void OnLogEvent(object sender, LogEventArgs args)
        {
            if (LogEvent == null)
                return;

            LogEvent(sender, args);
        }

        protected internal virtual void OnPropertyChanged(object sender, PropertyChangedEventArgs args)
        {
            if (PropertyChanged == null)
                return;

            PropertyChanged(sender, args);
        }

        protected internal virtual void OnStartupCompleted(object sender, EventArgs args)
        {
            isStartup = false;

            if (StartupCompleted == null)
                return;

            StartupCompleted(sender, args);
        }

        protected static uint GetTaskGroupId(ref uint taskGroupId)
        {
            if ((int)taskGroupId == -1)
                taskGroupId = TaskFactory.CreateTaskGroupId();

            return taskGroupId;
        }

        protected void OnDiscoverEvent(object sender, DiscoveryEventArgs args)
        {
            Runtime.Invoke(new DiscoveryEventHandler(OnInternalDiscoveryEvent), sender, (object)args);
        }

        protected void OnInternalDiscoveryEvent(object sender, DiscoveryEventArgs args)
        {
            switch (args.changeType)
            {
                case DiscoveryChangeType.Add:
                case DiscoveryChangeType.Change:
                    var taskGroupId1 = uint.MaxValue;
                    DateTime? lastWriteDateTime;

                    if (args.contracts != null)
                    {
                        foreach (var ci in args.contracts)
                        {
                            ContractInfo contract;

                            if (repository.contracts.TryGetValue(ci.repositoryId, out contract))
                            {
                                lastWriteDateTime = ci.repositoryId.LastWriteTime;
                                var lastWriteTime = contract.repositoryId.LastWriteTime;

                                if ((lastWriteDateTime.HasValue != lastWriteTime.HasValue
                                    ? 1
                                    : (!lastWriteDateTime.HasValue
                                        ? 0
                                        : (lastWriteDateTime.GetValueOrDefault() != lastWriteTime.GetValueOrDefault()
                                            ? 1
                                            : 0))) != 0)
                                    repository.RemoveContract(contract, GetTaskGroupId(ref taskGroupId1));
                                else
                                    continue;
                            }

                            repository.AddContract(ci, GetTaskGroupId(ref taskGroupId1));
                        }
                    }

                    if (args.plugins == null)
                        break;

                    foreach (var pluginInfo in args.PluginInfos)
                    {
                        PluginInfo plugin;

                        if (repository.plugins.TryGetValue(pluginInfo.repositoryId, out plugin))
                        {
                            lastWriteDateTime = pluginInfo.repositoryId.LastWriteTime;
                            var lastWriteTime = plugin.repositoryId.LastWriteTime;

                            if ((lastWriteDateTime.HasValue != lastWriteTime.HasValue
                                ? 1
                                : (!lastWriteDateTime.HasValue
                                    ? 0
                                    : (lastWriteDateTime.GetValueOrDefault() != lastWriteTime.GetValueOrDefault()
                                        ? 1
                                        : 0))) != 0)
                                repository.RemovePlugin(plugin, GetTaskGroupId(ref taskGroupId1));
                            else
                                continue;
                        }

                        repository.AddPlugin(pluginInfo, GetTaskGroupId(ref taskGroupId1));
                    }
                    break;

                case DiscoveryChangeType.Remove:
                    var taskGroupId = uint.MaxValue;

                    if (args.contracts != null)
                        foreach (var contractInfo in args.contracts.
                            Where(contractInfo => repository.contracts.ContainsKey(contractInfo.repositoryId)))
                            repository.RemoveContract(contractInfo, GetTaskGroupId(ref taskGroupId));

                    if (args.plugins == null)
                        break;

                    foreach (var pluginInfo in args.plugins.
                        Where(pluginInfo => repository.plugins.ContainsKey(pluginInfo.repositoryId)))
                        repository.RemovePlugin(pluginInfo, GetTaskGroupId(ref taskGroupId));

                    break;
            }
        }

        internal void InvokeEventHandler(string methodDescription, MulticastDelegate multicastDelegate,
            object sender, object args)
        {
            foreach (var eventDelegate in multicastDelegate.GetInvocationList().
                Where(eventDelegate => Array.IndexOf(multicastDelegate.GetInvocationList(), eventDelegate) >= 0))
                InvokeEventHandler(methodDescription, eventDelegate, sender, args);
        }

        internal void InvokeCancelEventHandler(string methodDescription, MulticastDelegate mulitcastDelegate,
            object sender, ICancelEventArgs args)
        {
            foreach (var eventDelegate in mulitcastDelegate.GetInvocationList().
                TakeWhile(eventDelegate => !args.Cancel).
                Where(eventDelegate => Array.IndexOf(mulitcastDelegate.GetInvocationList(), eventDelegate) >= 0))
                InvokeEventHandler(methodDescription, eventDelegate, sender, args);
        }

        internal void InvokeEventHandler(string methodDescription, Delegate eventDelegate, object sender, object args)
        {
            try
            {
                eventDelegate.DynamicInvoke(sender, args);
            }
            catch (Exception ex)
            {
                var exception = ex;
                string name1;
                string name2;

                if (eventDelegate.Target is ReflectionEventHandler)
                {
                    var reflectHandler = (ReflectionEventHandler)eventDelegate.Target;
                    name1 = reflectHandler.methodInfo.ReflectedType.Name;
                    name2 = reflectHandler.methodInfo.Name;
                }
                else
                {
                    name1 = eventDelegate.Target.GetType().Name;
                    name2 = eventDelegate.Method.Name;
                }

                while (exception.InnerException != null)
                    exception = exception.InnerException;

                Runtime.OnLogEvent(
                    this,
                    new LogEventArgs(
                        MessageType.Error,
                        VerbosityLevel.Normal,
                        Messages.UnhandledExceptonInEventHandler,
                        new object[]
                            {
                                methodDescription,
                                string.Format(
                                    "\n  {0}.{1}({2} sender, {3} args):\n",
                                    (object) name1,
                                    (object) name2,
                                    (object) sender.GetType().Name,
                                    (object) args.GetType().Name),
                                exception
                            }));
            }
        }

        internal static void LogQualifyResult(RepositoryMetaElement sender, QualificationState qualificationState,
            QualificationState unqualifiedState, QualificationState warningState, string formatMessage, string errMessage)
        {
            if ((qualificationState & unqualifiedState) == QualificationState.None)
            {
                if ((qualificationState & warningState) == QualificationState.None)
                    Runtime.OnLogEvent(
                        sender,
                        new LogEventArgs(
                            MessageType.Info,
                            VerbosityLevel.Diagnostic,
                            formatMessage,
                            new object[]
                                {
                                    sender.id.ToString(CultureInfo.InvariantCulture),
                                    sender.Name,
                                    Messages.QualifyResultOk,
                                    errMessage
                                }));
                else
                    Runtime.OnLogEvent(
                        sender,
                        new LogEventArgs(
                            MessageType.Warning,
                            VerbosityLevel.Normal,
                            formatMessage,
                            new object[]
                                {
                                    sender.id.ToString(CultureInfo.InvariantCulture),
                                    sender.Name,
                                    Messages.QualifyResultWarning,
                                    errMessage
                                }));
            }
            else
                Runtime.OnLogEvent(
                    sender,
                    new LogEventArgs(
                        MessageType.Error,
                        VerbosityLevel.Normal,
                        formatMessage,
                        new object[]
                            {
                                sender.id.ToString(CultureInfo.InvariantCulture),
                                sender.Name,
                                Messages.QualifyResultFailed,
                                errMessage
                            }));
        }

        private void ReleaseRest(Assembly initiator)
        {
            for (var count = repository.ExtensionInfos.Count; count > 1; count = repository.ExtensionInfos.Count)
            {
                for (var i = 1; i < repository.ExtensionInfos.Count; ++i)
                {
                    if (repository.ExtensionInfos[i].IsPlugged)
                        continue;

                    repository.ExtensionInfos[i].InternalRelease(initiator, true);
                    break;
                }

                if (repository.ExtensionInfos.Count >= count)
                    break;
            }

            foreach (var extensionTypeInfo in Repository.ExtensionTypeInfos)
                for (var i = extensionTypeInfo.extensionInfos.Count - 1; i >= 0; --i)
                    extensionTypeInfo.extensionInfos[i].InternalRelease(initiator, true);
        }
    }
}

